package simpledb.util;

import simpledb.common.Database;
import simpledb.common.DbException;
import simpledb.common.Permissions;
import simpledb.storage.DbFileIterator;
import simpledb.storage.HeapPage;
import simpledb.storage.HeapPageId;
import simpledb.storage.Tuple;
import simpledb.transaction.TransactionAbortedException;
import simpledb.transaction.TransactionId;

import java.util.NoSuchElementException;

/**
 * HeapFileIterator 实现了 DbFileIterator 接口，用于遍历堆文件（heap file）中的所有元组
 * 通过内部使用一个假设名为 PageCachePool 的缓存池对象，它可以有效地管理从磁盘加载到内存中的页面
 * 每个 HeapFileIterator 实例都关联了一个事务 ID (TransactionId)，这确保了在并发环境中，不同的事务可以独立地遍历相同的数据而不互相干扰
 */
public class HeapFileIterator implements DbFileIterator {

    // 标识整个堆文件中页面的总数
    private final int           totalPage;
    // 当前事务的 Id
    private final TransactionId transactionId;
    // 堆文件对应的表 Id
    private final int           tableId;
    // 当前正在处理的页面编号
    private int                 currentPageId;
    // 缓存池对象，用于缓存页面数据
    private PageCachePool       pageCachePool;

    public HeapFileIterator(final int totalPages, final TransactionId transactionId, final int tableId) {
        this.totalPage = totalPages;
        this.transactionId = transactionId;
        this.tableId = tableId;
    }

    /**
     * 创建一个新的 PageCachePool 对象，将当前页面编号设置为 0
     * 调用 cacheFilePages() 方法加载第一批页面到缓存池中
     * @throws DbException
     * @throws TransactionAbortedException
     */
    @Override
    public void open() throws DbException, TransactionAbortedException {
        this.pageCachePool = new PageCachePool(0.2, this.totalPage);
        this.currentPageId = 0;
        cacheFilePages();
    }

    /**
     * 从当前页面编号开始，尽可能多地缓存页面到 pageCachePool 中
     * 1、清空现有的缓存池
     * 2、遍历从 currentPageId 开始的最大缓存数量的页面，并将它们添加到缓存池中
     * 3、使用 Database.getBufferPool().getPage() 获取页面
     * 4、更新 currentPageId 到下一个需要加载的页面编号
     * 5、初始化缓存池（可能是为了准备下一轮的读取）
     */
    public void cacheFilePages() {
        this.pageCachePool.clear();
        int i = this.currentPageId;
        for (; i < this.currentPageId + this.pageCachePool.getMaxCacheNum() && i < this.totalPage; i++) {
            try {
                final HeapPageId pageId = new HeapPageId(this.tableId, i);
                final HeapPage page = (HeapPage) Database.getBufferPool().getPage(this.transactionId, pageId,
                    Permissions.READ_ONLY);
                this.pageCachePool.addPage(page.iterator());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        this.currentPageId = i;
        this.pageCachePool.init();
    }

    /**
     * 判断是否还有更多的元组可以迭代
     * 1、如果缓存池为空，则返回 false
     * 2、检查缓存池中是否有更多元素
     * 3、如果没有更多元素，则尝试加载新的页面直到找到有更多元素或所有页面都已加载完毕
     * @return
     * @throws DbException
     * @throws TransactionAbortedException
     */
    @Override
    public boolean hasNext() throws DbException, TransactionAbortedException {
        if (this.pageCachePool == null) {
            return false;
        }
        if (this.pageCachePool.hasNext()) {
            return true;
        }
        while (!this.pageCachePool.hasNext()) {
            cacheFilePages();
            if (this.totalPage == this.currentPageId) {
                break;
            }
        }
        return this.pageCachePool.hasNext();
    }

    /**
     * 返回下一个元组
     * 1、调用 hasNext() 检查是否存在更多元素。
     * 2、如果存在，则从缓存池中取出下一个元组。
     * 3、如果不存在，则抛出 NoSuchElementException 异常。
     * @return
     * @throws DbException
     * @throws TransactionAbortedException
     * @throws NoSuchElementException
     */
    @Override
    public Tuple next() throws DbException, TransactionAbortedException, NoSuchElementException {
        if (hasNext()) {
            return this.pageCachePool.next();
        }
        throw new NoSuchElementException("The Iterator don't have more elements");
    }

    /**
     * 重置迭代器回到初始状态。直接调用 open() 方法重新打开迭代器。
     * @throws DbException
     * @throws TransactionAbortedException
     */
    @Override
    public void rewind() throws DbException, TransactionAbortedException {
        open();
    }

    /**
     * 关闭迭代器，释放资源。清空缓存池并将其设为 null。
     */
    @Override
    public void close() {
        if (this.pageCachePool != null) {
            this.pageCachePool.clear();
            this.pageCachePool = null;
        }
    }
}
